#include "Stdafx.h"
#include "EditBox.h"
#include "IMEDevice.h"

const DWORD WHITECOLOR = D3DCOLOR_XRGB(255, 255, 255);

const unsigned int MAX_TEXTBUFFER = 256;

namespace 
{
	int RectWidth(const RECT& rect)
	{
		return rect.right - rect.left;
	}
}

CEditBox::CEditBox(void)
: m_pIME(NULL)
, m_pFont(NULL)
, m_pCaretLine(NULL)
, m_kTextBuffer(MAX_TEXTBUFFER)
, m_kCompositBuffer(MAX_TEXTBUFFER)
, m_nCaretPos(0)
, m_nFirstVisible(0)
, m_nSelStart(0)
, m_fBlankCaretTime(0.0f)
{
}

CEditBox::~CEditBox(void)
{
	if (m_pFont)
	{
		m_pFont->Release();
		m_pFont = NULL;
	}

	if (m_pCaretLine)
	{
		m_pCaretLine->Release();
		m_pCaretLine = NULL;
	}

	if (m_pIME)
	{
		delete m_pIME;
		m_pIME = NULL;
	}
}

bool CEditBox::Create(HWND hWnd, LPDIRECT3DDEVICE9 device, const RECT& rcPanel, const char* fontface)
{
	// 1. copy panel rectangle
	CopyMemory(&m_rcPanel, &rcPanel, sizeof(RECT));

	const UINT OFFSET = 7;
	m_rcText.left = m_rcPanel.left + OFFSET;
	m_rcText.top = m_rcPanel.top + OFFSET;
	m_rcText.right = m_rcPanel.right - OFFSET;
	m_rcText.bottom = m_rcPanel.bottom - OFFSET;

	// 2. create font
	D3DXFONT_DESC fontDesc;
	fontDesc.Height					= GetStringHeight();
//	fontDesc.Width					= 12;
	fontDesc.Weight					= FW_NORMAL;
	fontDesc.MipLevels				= D3DX_DEFAULT;
	fontDesc.Italic					= FALSE;
	fontDesc.CharSet				= DEFAULT_CHARSET;
	fontDesc.OutputPrecision		= OUT_DEFAULT_PRECIS;
	fontDesc.Quality				= DEFAULT_QUALITY;
	fontDesc.PitchAndFamily			= FF_DONTCARE;
	strcpy_s( fontDesc.FaceName, LF_FACESIZE, fontface );
	if (FAILED(D3DXCreateFontIndirect( device, &fontDesc, &m_pFont )))
	{
		return false;
	}

	m_kTextBuffer.SetFont(m_pFont);
	m_kCompositBuffer.SetFont(m_pFont);

	// 3. create caret line
	if (FAILED(D3DXCreateLine(device, &m_pCaretLine)))
	{
		if (m_pFont)
		{
			m_pFont->Release();
			m_pFont = NULL;
		}
	}

	m_pCaretLine->SetAntialias(true);
	m_pCaretLine->SetWidth(2.0f);

	// 4. create ime system
	m_pIME = new CIMEDevice(this);
	m_pIME->Init(hWnd);

	return true;
}

void CEditBox::Update(float fDeltaTime)
{
	m_fBlankCaretTime += fDeltaTime * 5.0f;
	m_bFlipCaret = sinf(m_fBlankCaretTime) < 0.0f ? false : true;
}

void CEditBox::Render()
{
	RenderControl();

	if (m_kTextBuffer.GetTextSize() > 0)
	{
		RenderText();
	}

	RenderCaret();
	RenderSelectedRegion();
}

bool CEditBox::MsgProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
	bool bRet = true;

	switch(message)
	{
	case WM_INITDIALOG:
		{
			if (m_pIME == NULL)
			{

			}
		}
		break;
	case WM_COMMAND:
		{
			DWORD dwVirtualKey = (DWORD)wParam;
			DWORD dwLoword = LOWORD(wParam);
// 			switch(LOWORD(wParam))
// 			{
// 			case IDM_FILE_COPY:
// 				m_pIMEDevice->CopyToClipboard();
// 				break;
// 			case IDM_FILE_PASTE:
// 				m_pIMEDevice->PasteFromClipboard();
// 				break;
// 			}
		}
		break;
	case WM_KEYDOWN:
		{
			switch(wParam)
			{
			case VK_HOME:
				PlaceCaret( 0 );
				break;
			case VK_END:
				PlaceCaret( m_kTextBuffer.GetTextSize() );
				break;
			case VK_INSERT:
				if( GetKeyState( VK_CONTROL ) < 0 )
				{
					// Control Insert. Copy to clipboard
					CopyToClipboard();
				} 
				else if( GetKeyState( VK_SHIFT ) < 0 )
				{
					// Shift Insert. Paste from clipboard
					PasteFromClipboard();
				}
				break;
			case VK_DELETE:
				RemoveChar(GetCaretPos(), false);
				break;
			case VK_LEFT:
				if( GetKeyState( VK_CONTROL ) < 0 )
				{
					m_kTextBuffer.GetPriorItemPos( m_nCaretPos, &m_nCaretPos );
					PlaceCaret( m_nCaretPos );
				}
				else if( m_nCaretPos > 0 )
				{
					PlaceCaret( m_nCaretPos - 1 );
				}

				if( GetKeyState( VK_SHIFT ) >= 0 )
				{
					m_nSelStart = m_nCaretPos;
				}
				break;
			case VK_RIGHT:
				if( GetKeyState( VK_CONTROL ) < 0 )
				{
					m_kTextBuffer.GetNextItemPos( m_nCaretPos, &m_nCaretPos );
					PlaceCaret( m_nCaretPos );
				}
				else if( m_nCaretPos < m_kTextBuffer.GetTextSize() )
				{
					PlaceCaret( m_nCaretPos + 1 );
				}

				if( GetKeyState( VK_SHIFT ) >= 0 )
				{
					m_nSelStart = m_nCaretPos;
				}
				break;
			}
		}
		break;
	case WM_LBUTTONDOWN:
		{
			int nX = (short)LOWORD(lParam);
			int nY = (short)HIWORD(lParam);

			int nRelativeX = nX - m_rcText.left;
			PlaceCaret(GetWidthFromCharPosition(nRelativeX));
		}
		break;
	default:
		{
			if (m_pIME)
			{
				bRet = m_pIME->MsgProc(message, wParam, lParam);
			}
		}
		break;
	}

	return bRet;
}

void CEditBox::RenderControl()
{

}

void CEditBox::RenderText()
{
	if (m_kTextBuffer.GetTextSize() > 0)
	{
		WCHAR* pVisibleText = NULL;

		if (m_nFirstVisible > 0)
		{
			unsigned int uiTextLength = m_kTextBuffer.GetTextSize();
			const WCHAR* textBuffer = m_kTextBuffer.GetBuffer();

			pVisibleText = const_cast<WCHAR*>(&textBuffer[m_nFirstVisible]);
		}
		else
		{
			pVisibleText = const_cast<WCHAR*>(m_kTextBuffer.GetBuffer());
		}

		assert(pVisibleText != NULL);
		m_pFont->DrawTextW(NULL, pVisibleText, -1, &m_rcText, DT_LEFT, WHITECOLOR );
	}
}

void CEditBox::RenderCaret()
{
	if (m_bFlipCaret)
	{
		UINT uiTextWidth = GetStringWidth(m_nFirstVisible, m_nCaretPos);

		const float fStartX = (float)m_rcText.left + uiTextWidth;
		const float fTop = (float)m_rcText.top;
		const float fBottom = (float)m_rcText.bottom;

		D3DXVECTOR2 caretLine[2] = {
			D3DXVECTOR2(fStartX, fTop),
			D3DXVECTOR2(fStartX, fTop + GetStringHeight())
		};

		if ( SUCCEEDED(m_pCaretLine->Begin()) )
		{
			m_pCaretLine->Draw(&caretLine[0], 2, WHITECOLOR);

			m_pCaretLine->End();
		}
	}
}

void CEditBox::RenderSelectedRegion()
{
	if (m_nSelStart != m_nCaretPos)
	{

	}
}

int CEditBox::GetStringWidth(int nStartPos /*= 0*/, int nCaretPos /*= -1*/ )
{
	if ( nCaretPos == -1 )
	{
		nCaretPos = m_nCaretPos;
	}

	int nX, nX2;
	m_kTextBuffer.CPtoX(nStartPos, false, &nX);
	m_kTextBuffer.CPtoX(nCaretPos, false, &nX2);

	return nX2 - nX;
}

int CEditBox::GetWidthFromCharPosition(const int nX )
{
	int	nTrailing = 0; 
	int	nPos;	
	m_kTextBuffer.XtoCP(nX, &nPos, &nTrailing);

	int charPos = nPos + nTrailing;
	if (charPos > m_kTextBuffer.GetTextSize())
		charPos = m_kTextBuffer.GetTextSize();

	int snappedToX = 0;
	if(m_kTextBuffer.CPtoX(nPos, nTrailing, &snappedToX) != S_OK)
	{
		int nX, nY;
		m_kTextBuffer.GetSize(nX, nY);

		snappedToX = nX;
	}

	return charPos;
}

void CEditBox::PlaceCaret(const int nCP )
{
	assert(nCP >= 0 && nCP <= m_kTextBuffer.GetTextSize());
	m_nCaretPos = nCP;
	ResetBlankCaret();

	int nX1st, nX, nX2;
	m_kTextBuffer.CPtoX(m_nFirstVisible, false, &nX1st);
	m_kTextBuffer.CPtoX(nCP, false, &nX);

	// if nCP is NULL terminator, get the leading edge instead of trailing.
	if (nCP == m_kTextBuffer.GetTextSize())
	{
		nX2 = nX;
	}
	else
	{
		m_kTextBuffer.CPtoX(nCP, true, &nX2);	// trailing
	}

	// If the left edge of the char is smaller than the left edge of the 1st visible char,
	// we need to scroll left until this char is visible.
	if( nX < nX1st )
	{
		// Simply make the first visible character the char at the new caret position.
		m_nFirstVisible = nCP;
	}
	else
		// If the right of the character is bigger than the offset of the control's
		// right edge, we need to scroll right to this character.
		if( nX2 > nX1st + RectWidth( m_rcText ) )
		{
			// Compute the X of the new left-most pixel
			int nXNewLeft = nX2 - RectWidth( m_rcText );

			// Compute the char position of this character
			int nCPNew1st, nNewTrail;
			m_kTextBuffer.XtoCP( nXNewLeft, &nCPNew1st, &nNewTrail );

			// If this coordinate is not on a character border,
			// start from the next character so that the caret
			// position does not fall outside the text rectangle.
			int nXNew1st;
			m_kTextBuffer.CPtoX( nCPNew1st, FALSE, &nXNew1st );
			if( nXNew1st < nXNewLeft )
				++nCPNew1st;

			m_nFirstVisible = nCPNew1st;
		}
}

void CEditBox::CopyToClipboard()
{
	// Copy the selection text to the clipboard
	if( m_nCaretPos != m_nSelStart && OpenClipboard( NULL ) )
	{
		EmptyClipboard();

		HGLOBAL hBlock = GlobalAlloc( GMEM_MOVEABLE, sizeof(WCHAR) * ( m_kTextBuffer.GetTextSize() + 1 ) );
		if( hBlock )
		{
			WCHAR *pwszText = (WCHAR*)GlobalLock( hBlock );
			if( pwszText )
			{
				int nFirst = __min( m_nCaretPos, m_nSelStart );
				int nLast = __max( m_nCaretPos, m_nSelStart );
				if( nLast - nFirst > 0 )
					CopyMemory( pwszText, m_kTextBuffer.GetBuffer() + nFirst, (nLast - nFirst) * sizeof(WCHAR) );
				pwszText[nLast - nFirst] = L'\0';  // Terminate it
				GlobalUnlock( hBlock );
			}
			SetClipboardData( CF_UNICODETEXT, hBlock );
		}
		CloseClipboard();
		// We must not free the object until CloseClipboard is called.
		if( hBlock )
			GlobalFree( hBlock );
	}
}


void CEditBox::PasteFromClipboard()
{
	//	DeleteSelectionText();

	if( OpenClipboard( NULL ) )
	{
		HANDLE handle = GetClipboardData( CF_UNICODETEXT );
		if( handle )
		{
			// Convert the ANSI string to Unicode, then
			// insert to our buffer.
			WCHAR *pwszText = (WCHAR*)GlobalLock( handle );
			if( pwszText )
			{
				// Copy all characters up to null.
				if( m_kTextBuffer.InsertString( m_nCaretPos, pwszText ) )
					PlaceCaret( m_nCaretPos + lstrlenW( pwszText ) );
				m_nSelStart = m_nCaretPos;
				GlobalUnlock( handle );
			}
		}
		CloseClipboard();
	}
}

void CEditBox::InsertChar(const int nCP, const std::wstring wtext)
{
	int nIP = nCP;
	for (unsigned int i = 0; i < wtext.size(); ++i)
	{
		if (m_kTextBuffer.InsertChar(nIP, wtext[i]))
		{
			PlaceCaret(nIP + 1);
			nIP = GetCaretPos();
		}
	}
}

void CEditBox::InsertChar(const int nCP, const WCHAR wChar)
{
	if (m_kTextBuffer.InsertChar(nCP, wChar))
	{
		PlaceCaret(nCP + 1);
	}
}

void CEditBox::RemoveChar(const int nCP, bool bPrior)
{
	if (bPrior)
	{
		if (m_kTextBuffer.RemoveChar(nCP - 1))
		{
			PlaceCaret(nCP - 1);
		}
	}
	else
	{
		m_kTextBuffer.RemoveChar(nCP);
	}
}